﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Threading;
using System.Windows.Forms;

/*
	Todo:
	Larger programs
	- add a filter clause option. 
		- doubleclick selects field
		- Filter button
		- incorporate filter logic. Deal with syntax errors.
		- move fields 
*/
namespace DbView
{
	public partial class ExportCsvForm : Form
	{
		public ExportCsvForm(DbViewDriver.IDatabaseExportSource database, string table, string file, ViewDefinition viewDef)
		{
			Initialise(database, table, file);
			if (viewDef != null)
			{
				InitialiseFromView(viewDef);
			}
		}


		void Initialise(DbViewDriver.IDatabaseExportSource database, string table, string file)
		{
			InitializeComponent();
			Database = database;
			TableName = table;
			OutputCsvFile = file;

			lblHelp.Text = TITLE;
			lblTableName.Text = TableName;
			lblFileName.Text = OutputCsvFile;
		}

		private void InitialiseFromView(ViewDefinition viewDef)
		{
			// extract fields from viewdef
			foreach (SchemaField f in viewDef.DisplayFields)
			{
				listSelectedFields.Items.Add(f.Name);
			}
			if (viewDef.ExtraClause.Length > 0)
			{
				this.textFilter.Text = viewDef.ExtraClause;
			}
			if (viewDef.FilterClause.Length > 0)
			{
				this.textFilter.Text = (viewDef.ExtraClause.Length > 0) ? string.Format("({0}) and ({1})", viewDef.ExtraClause, viewDef.FilterClause) :
										viewDef.FilterClause;
			}
			string sort = "";
			foreach (SchemaField f in viewDef.SortFields)
			{
				if (sort.Length > 0) sort += ", ";
				sort += f.Name;
				if (f.Ord == "D") sort += " desc";
			}
			txtSort.Text = sort;
		}


		DbViewDriver.IDatabaseExportSource Database { get; set; }
		string TableName { get; set; }
		string OutputCsvFile { get; set; }

		private void ExportCsvForm_Load(object sender, EventArgs e) { SetupControls(); }
		private void button1_Click(object sender, EventArgs e) { this.Close(); }
		private void button2_Click(object sender, EventArgs e)
		{
			if (IsWorking())
				DoCancel();
			else
				DoExport();
		}

		private void SetupControls()
		{
			// default export headings. To make export and import work smoothly in DbViewSharp
			chkHeadings.Checked = true;
			try
			{
				// get field list
				List<String> fieldNames = GetFieldNames(TableName);
				foreach (String fieldName in fieldNames)
					listFields.Items.Add(fieldName);
				// fields may already be populated
				if (listSelectedFields.Items.Count == 0)
					foreach (String fieldName in fieldNames)
						listSelectedFields.Items.Add(fieldName);
			}
			catch (Exception e)
			{
				string msg = @"Error preparing form for export configuration.
There was a problem accessing the field information of the table. 
Please check the message that follows and make the appropriate adjustments

{0}";
				MessageBox.Show(String.Format(msg, e.Message), "Error fetching table column data", MessageBoxButtons.OK, MessageBoxIcon.Error);
				this.Close();
			}


			UpdateGUI(0);
		}

		// used to control appearence a. when list items change; b. when running the export
		private void UpdateGUI(int state)
		{
			switch (state)
			{
				case 0:
					bool allSelected = (listFields.Items.Count == listSelectedFields.Items.Count);
					bool noneSelected = (listSelectedFields.Items.Count == 0);
					bool overOneSelected = (listSelectedFields.Items.Count > 1);
					btnAll.Enabled = !allSelected;
					btnAdd.Enabled = !allSelected;
					btnNone.Enabled = !noneSelected;
					btnRemove.Enabled = !noneSelected;
					btnExport.Enabled = !noneSelected;
					btnUp.Enabled = overOneSelected;
					btnDown.Enabled = overOneSelected;
					btnExport.Text = "E&xport";
					// if called following export re-enable
					if (!btnClose.Enabled)
					{
						btnClose.Enabled = true;
						btnExport.Enabled = true;
						btnBrowse.Enabled = true;
						btnFilter.Enabled = true;
						btnSort.Enabled = true;
						listFields.Enabled = true;
						listSelectedFields.Enabled = true;
					}
					break;
				case 1: // export is running
					btnAll.Enabled = false;
					btnAdd.Enabled = false;
					btnNone.Enabled = false;
					btnRemove.Enabled = false;
					btnClose.Enabled = false;
					//btnExport.Enabled = false;
					btnExport.Text = "Cancel";
					btnBrowse.Enabled = false;
					btnFilter.Enabled = false;
					btnSort.Enabled = false;
					btnUp.Enabled = false;
					btnDown.Enabled = false;
					listFields.Enabled = false;
					listSelectedFields.Enabled = false;
					break;
			}

		}

		private void DoExport()
		{
			string message = "";

			// Check whether file exists
			if (File.Exists(OutputCsvFile))
			{
				message = "File " + OutputCsvFile + " exists.\nIs it OK to overwrite it.";
				DialogResult res = MessageBox.Show(this, message, "Confirm overwrite", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
				if (res != DialogResult.Yes) return; // No / Cancel aborts
			}

			Thread t = new Thread(new ParameterizedThreadStart(ExportCsvForm.DoExport)); // create new thread with this object
			t.IsBackground = true;												// make it a daemon - prevent thread callback issues 

			// SetUI(1);			// disable controls you dont want fiddling with (rememer [x] button
			d = Progress;		// assign the delegate (can't be done at declaration for some reason.
			this.m_Cancel = false;	// reset the cancel flag

			t.Start(this);		// Start the work
		}

		private void DoCancel()
		{
			if (MessageBox.Show(this, "Cancel Export", "Cancel", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes)
				this.m_Cancel = true;
		}

		public static void DoExport(object caller)
		{
			// dereference the current form (See where the thread is created)
			ExportCsvForm form = caller as ExportCsvForm;
			String[] fields = form.GetSelectedFields();
			string fieldList = "[" + String.Join("],[", fields) + "]";
			string filter = form.textFilter.Text;
			string sort = form.txtSort.Text;

			form.Invoke(form.d, new object[] { "Fetching data ...", 0, 100, 1 });
			// Fetch the data. All in one go for now. It's quite fast and will require an upgrade to the interface.
			// Later it's inevitable
			try
			{
				StringWriter writer = new StringWriter();
				System.Text.Encoding fileEncoding = (form.chkUnicode.Checked) ? System.Text.Encoding.Unicode : System.Text.Encoding.Default;
				TextWriter fileWriter = new StreamWriter(form.OutputCsvFile, false, fileEncoding);

				bool first = true;
				// write the header if required
				if (form.chkHeadings.Checked)
				{
					foreach (String field in fields)
					{
						if (!first) writer.Write(",");
						writer.Write(form.CsvField(field));
						if (first) first = false;
					}
					writer.Write("\n");
				}

				// then data
				int rows = form.Database.ReadInit(form.TableName, fields, filter, sort);
				int progressEvery = rows / 50;
				++progressEvery; // remove div by 0 worry
				object[] data;
				int rowsWritten = 0;
				bool userCancel = false;
				while (form.Database.ReadNext(out data) && !userCancel)
				{
					first = true;
					int idx = 0;
					foreach (String field in fields)
					{
						if (!first) writer.Write(",");
						writer.Write(form.CsvField(data[idx++].ToString()));
						if (first) first = false;
					}
					writer.Write("\n");
					if ((++rowsWritten % progressEvery) == 0)
						userCancel = (bool)form.Invoke(form.d, new object[] { "Write to file ...", rowsWritten, rows, 1 });
				}
				fileWriter.Write(writer.ToString());
				writer.Close();
				fileWriter.Close();

				form.BeginInvoke(form.d, new object[] { "Finished", (userCancel) ? 0 : rows, rows, (userCancel) ? 2 : 0 }); // to quit
			}
			catch (Exception exc)
			{
				form.BeginInvoke(form.d, new object[] { exc.Message, 0, 1, -1 }); // register error
			}
			finally
			{
				form.Database.ReadClose();
			}
		}

		bool Progress(String op, int step, int steps, int state)
		{
			if (ExportProgress.Maximum != steps)
				ExportProgress.Maximum = steps;
			ExportProgress.Value = step;

			switch (state)
			{
				case -1: // Error
					{
						string message = "Error during export. The message below should indicate the\n" +
										"problem area. If you specified a filter please check\n" +
										"that carefully. The error reported is:\n" + op;
						MessageBox.Show(this, message, "Error in Export from " + TableName, MessageBoxButtons.OK, MessageBoxIcon.Error);
						UpdateGUI(0);
						break;
					}
				case 1:
					UpdateGUI(1);
					if (this.m_Cancel) return true; // cancel
					break; // running
				case 0:
					UpdateGUI(0);
					MessageBox.Show(this, "Export to " + OutputCsvFile + " complete", "Export from " + TableName, MessageBoxButtons.OK, MessageBoxIcon.Information);
					this.Close();
					break; // done
				case 2:    // user cancel not implemented yet.
					UpdateGUI(0);
					MessageBox.Show(this, "Export was cancelled. " + OutputCsvFile + " may be incomplete", "Export from " + TableName, MessageBoxButtons.OK, MessageBoxIcon.Information);
					break; // cancelled
			}
			return false;
		}

		// Work state waiting for form close protection
		// Work state 
		bool IsWorking()
		{
			return (this.btnExport.Text == "Cancel"); // hacky.
		}


		private string CsvField(string field)
		{
			/* A CSV field needs to be double-quoeted if it contains:
				a) , or "
				b) a line feed character char(13) or char(10)
			*/
			bool wantQuotes = (field.IndexOf('"') != -1 || field.IndexOf(',') != -1 ||
								field.IndexOf('\n') != -1 || field.IndexOf('\r') != -1);
			if (!wantQuotes) return field;
			return "\"" + field.Replace("\"", "\"\"") + "\"";
		}

		private String[] GetSelectedFields()
		{
			List<String> selectedFields = new List<string>();
			foreach (String field in listSelectedFields.Items)
				selectedFields.Add(field);
			return selectedFields.ToArray();
		}

		private List<string> GetFieldNames(string tableName)
		{
			DbView.FieldInfo[] fields = Database.GetSchema(TableName);
			List<String> l = new List<String>();
			foreach (DbView.FieldInfo field in fields)
			{
				l.Add(field.Name);
			}
			return l;
		}

		private void btnBrowse_Click(object sender, EventArgs e)
		{
			OutputCsvFile = GetFileName(lblFileName.Text);
			lblFileName.Text = OutputCsvFile;
		}

		private string GetFileName(String prevFileName)
		{
			SaveFileDialog createFileDialog = new SaveFileDialog();

			createFileDialog.FileName = Path.GetFileName(prevFileName);
			createFileDialog.Filter = "CSV Files (*.csv)|*.csv";
			createFileDialog.RestoreDirectory = true;

			if (createFileDialog.ShowDialog() == DialogResult.OK)
			{
				return createFileDialog.FileName;

			}
			return prevFileName;
		}

		//////////////////////////////////////////////////////////////////////////////
		// field selection and de-selection
		private void btnRemove_Click(object sender, EventArgs e)
		{
			if (listSelectedFields.SelectedIndex == -1)
				MessageBox.Show("Please select a field on the right to remove before pressing this button", "Help", MessageBoxButtons.OK, MessageBoxIcon.Information);
			else
			{
				int holdSelectedIndex = listSelectedFields.SelectedIndex;
				string item = listSelectedFields.Items[listSelectedFields.SelectedIndex].ToString();
				listSelectedFields.Items.Remove(item);
				// set selected index to what it was before
				if (holdSelectedIndex < listSelectedFields.Items.Count)
					listSelectedFields.SelectedIndex = holdSelectedIndex;
				else
					listSelectedFields.SelectedIndex = holdSelectedIndex - 1;

				UpdateGUI(0);
			}
		}

		private void btnAdd_Click(object sender, EventArgs e)
		{
			if (listFields.SelectedIndex == -1)
				MessageBox.Show("Please select a field on the left to add before pressing this button", "Help", MessageBoxButtons.OK, MessageBoxIcon.Information);
			else
			{
				string item = listFields.Items[listFields.SelectedIndex].ToString();
				if (listSelectedFields.Items.Contains(item))
					MessageBox.Show("The field is already selected and cannot be exported in two columns", "Help", MessageBoxButtons.OK, MessageBoxIcon.Information);
				else
				{
					listSelectedFields.Items.Add(item);
					// move selected index on (unless at end)
					if (listFields.SelectedIndex < listFields.Items.Count - 1)
						++listFields.SelectedIndex;
					UpdateGUI(0);
				}
			}

		}

		private void btnNone_Click(object sender, EventArgs e)
		{
			listSelectedFields.Items.Clear();
			// if field index is -1 set it to 0
			UpdateGUI(0);
		}

		private void btnAll_Click(object sender, EventArgs e)
		{
			foreach (String item in listFields.Items)
			{
				if (!listSelectedFields.Items.Contains(item))
					listSelectedFields.Items.Add(item);
			}
			// if selected field index is -1 set it to 0
			UpdateGUI(0);
		}
		//////////////////////////////////////////////////////////////////////////////
		private void btnFilter_Click(object sender, EventArgs e)
		{
			// get
			if (listFields.SelectedIndex >= 0)
			{
				string fieldName = listFields.SelectedItem.ToString();
				string filterText = textFilter.Text;
				filterText += ((filterText.Length == 0) ? "" : " ") + fieldName + " ";
				textFilter.Text = filterText;
				textFilter.Select(filterText.Length, 0);
				textFilter.Focus();
			}
		}

		private void btnSort_Click(object sender, EventArgs e)
		{
			// get
			if (listSelectedFields.SelectedIndex >= 0)
			{
				string fieldName = listSelectedFields.SelectedItem.ToString();
				string sortText = txtSort.Text;
				sortText += ((sortText.Length == 0) ? "" : ", ") + fieldName;
				txtSort.Text = sortText;
			}
		}

		private void btnUp_Click(object sender, EventArgs e)
		{
			int holdIndex = listSelectedFields.SelectedIndex;
			if (holdIndex >= 1)
			{
				string fieldName = listSelectedFields.SelectedItem.ToString();
				listSelectedFields.Items.RemoveAt(holdIndex--);
				listSelectedFields.Items.Insert(holdIndex, fieldName);
				listSelectedFields.SelectedIndex = holdIndex;
			}
		}

		private void btnDown_Click(object sender, EventArgs e)
		{
			int holdIndex = listSelectedFields.SelectedIndex;
			// not last and something selected
			if (holdIndex < listSelectedFields.Items.Count -1 &&
				holdIndex >= 0)  
			{
				string fieldName = listSelectedFields.SelectedItem.ToString();
				listSelectedFields.Items.RemoveAt(holdIndex++);
				listSelectedFields.Items.Insert(holdIndex, fieldName);
				listSelectedFields.SelectedIndex = holdIndex;
			}
		}



		// prevent user from exiting while the actual import is in progress,
		private void Form6_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (e.CloseReason == CloseReason.UserClosing && IsWorking())
			{
				MessageBox.Show(this, CANT_CLOSE, "Cannot close right now", MessageBoxButtons.OK, MessageBoxIcon.Warning);
				e.Cancel = true; // suppress. Better would be to ask
			}
		}

		delegate bool ProgDelegate(String operation, int step, int steps, int state);
		ProgDelegate d;
		bool m_Cancel = false;

		// help text for top of dialog.
		const string TITLE =
@"Export data from a table to a CSV file.  Press [...] to change the default file name or location. Use the buttons to select the fields to include in the file from the source table. By default all fields will be included. Restrict the rows to export by entering a search clause in the filter text box (omit the WHERE keyword). Check Unicode if you are exporting non-ascii data to Excel or Word. Press the Export button to create the file.
";
		const string CANT_CLOSE = "Program is still working. Please cancel the current operation before closing";




	}
}
